<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
<head>
  <title>
    在不重新编译的情况下直接修改Java Class文件中的内容 // 飞狐的部落格
  </title>

  <link href="http://gmpg.org/xfn/11" rel="profile">
<meta http-equiv="content-type" content="text/html; charset=utf-8">


<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">

<meta name="description" content="在不重新编译的情况下直接修改Java Class文件中的内容">
<meta name="keywords" content="Java,">
<meta name="author" content="飞狐">
<meta name="generator" content="Hugo 0.14" />

  <meta property="og:title" content="在不重新编译的情况下直接修改Java Class文件中的内容" />
<meta property="og:description" content="在不重新编译的情况下直接修改Java Class文件中的内容" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta property="og:url" content="http://lucumt.info/posts/modify-java-class-file-content-directly/" />


  
   <link rel="stylesheet" href="http://lucumt.info/css/base-min.css">
   <link rel="stylesheet" href="http://lucumt.info/css/pure-min.css">
  
  
    <link rel="stylesheet" href="http://lucumt.info/css/grids-responsive-min.css">
  
  

  <link rel="stylesheet" href="http://lucumt.info/css/redlounge.css">
  
  <link rel="stylesheet" href="http://lucumt.info/css/googleapis.font.css">
  <link rel="stylesheet" href="http://lucumt.info/css/jqcloud.css">
  <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
  

  
  <link rel="apple-touch-icon-precomposed" sizes="144x144" href="http://lucumt.info/touch-icon-144-precomposed.png">
  <link rel="shortcut icon" href="http://lucumt.info/lucumt.ico">

  
  <link href="" rel="alternate" type="application/rss+xml" title="飞狐的部落格" />

  <script src="http://lucumt.info//js/jquery.min.js"></script>
  <script src="http://lucumt.info//js/jqcloud-1.0.4.min.js"></script>

    
  
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.6.0/styles/rainbow.min.css">
  
  <script src="http://lucumt.info/js/highlight.min.js"></script>
  <script>hljs.initHighlightingOnLoad();</script>


  

  

  

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', "UA-75123653-1", 'auto');
  ga('send', 'pageview');
</script>




<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?cabc0a71f63da092412d82d1aefe7d1c";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>

</head>
<body>
	
		<div id="nav-to-top">
			<span class="decorative-marker">//</span><a href="#top">返回顶部</a>
		</div>
	

	<div id="layout" class="pure-g">
    
		 <a href="https://github.com/lucumt/ghblog" target="_new">
			<img style="position: absolute; top: 0; right: 0; border: 0;" 
			src="/img/forkme_right_red.png" alt="Fork me on GitHub">
		</a>

    <div class="sidebar pure-u-1 pure-u-md-1-4">
  <div class="header">
    

	
	  <img src="http://lucumt.info/img/photo.jpg" class="profilepic">
	

    <h1 class="brand-title">飞狐的部落格</h1>
    <h2 class="brand-tagline">Rosen Lu</h2>

    <nav class="nav">
      <ul class="nav-list">
        <li class="nav-item"><span class="nav-item-separator">//</span><a href="http://lucumt.info/">Home</a></li>
        
          <li class="nav-item"><span class="nav-item-separator">//</span><a href="http://lucumt.info/about/">About</a></li>
        
          <li class="nav-item"><span class="nav-item-separator">//</span><a href="http://lucumt.info/posts/">Blog</a></li>
        
      </ul>
    </nav>

    
    <div class="social-buttons">
      
        
        <a href="https://github.com/lucumt" target="_blank"><i class='fa fa-github'></i></a>
        
      
        
        <a href="https://plus.google.com/u/0/115794588502118898127/posts" target="_blank"><i class='fa fa-google-plus'></i></a>
        
      
        
        <a href="https://www.facebook.com/rosen.lu.1" target="_blank"><i class='fa fa-facebook'></i></a>
        
      
        
        <a href="http://www.twitter.com/rosenlucumt" target="_blank"><i class='fa fa-twitter'></i></a>
        
      
        
        <a href="mailto:lucumt@gmail.com" target="_blank"><i class='fa fa-mail-forward'></i></a>
        
      
    </div>
    
    
	
	  <hr class="nav-site-separator"/>
	  <nav class="nav">
      <ul class="nav-list">
	    
		   <li class="nav-site"><a href="http://lilydjwg.is-programmer.com/" target="_blank">依云的博客</a></li>
		
		   <li class="nav-site"><a href="http://evilbinary.org/" target="_blank">邪恶二进制</a></li>
		
		   <li class="nav-site"><a href="http://www.wlman.cc/" target="_blank">Consec &#39;s Blog</a></li>
		
		   <li class="nav-site"><a href="http://www.linuxzen.com/" target="_blank">cold&#39;s world</a></li>
		
		   <li class="nav-site"><a href="http://frantic1048.logdown.com/" target="_blank">Frantic log#1048</a></li>
		
	  </ul>
	  </nav>
	
	

  </div>
</div>

	
	

    <div class="content pure-u-1 pure-u-md-3-4">
		<a name="top"></a>
		

		
			
		    <div id="toc" class="pure-u-1 pure-u-md-1-4">
				<small class="toc-label">Contents</small>
		   	 	<nav id="TableOfContents">
<ul>
<li>
<ul>
<li><a href="#修改class文件中的变量:4e9cb51181f8e993ffa84b42e58265dc">修改Class文件中的变量</a></li>
<li><a href="#修改class文件中的方法:4e9cb51181f8e993ffa84b42e58265dc">修改Class文件中的方法</a></li>
<li><a href="#在class文件中增加方法:4e9cb51181f8e993ffa84b42e58265dc">在Class文件中增加方法</a></li>
<li><a href="#后记:4e9cb51181f8e993ffa84b42e58265dc">后记</a></li>
</ul></li>
</ul>
</nav>
		    </div>
		    
	    
  		<section class="post">
            <h1 class="post-title">
              <a href="http://lucumt.info/posts/modify-java-class-file-content-directly">在不重新编译的情况下直接修改Java Class文件中的内容</a>
            </h1>
            <h3 class="post-subtitle">
            	
            </h3>
            
            	<span class="post-date">
                	<span class="post-date-day"><sup>12</sup></span><span class="post-date-separator">/</span><span class="post-date-month">Aug</span> <span class="post-date-year">2017</span>
            	</span>
            	
            
            	
            		<span class="post-author-single">By <a class="post-author" href="https://www.linkedin.com/in/%E8%BF%90%E5%BC%BA-%E5%8D%A2-50a08bb5/" target="_new">飞狐</a></span>
            		




            	
            

			
			
				<div class="post-categories">
				
					<a class="post-category post-category-java" href="http://lucumt.info//categories/java">Java</a>
				
				</div>
			

			

			

            

<p>Java程序实际上执行的是Java文件编译后的Class文件，这是任何一个Java开发人员都了解的基本知识。若Java程序执行的结果不符合要求，通常的解决方法是先修改Java文件，重新编译成Class文件后再次执行。但有时候我们不能直接修改Java文件（如只有包含class文件的jar包），此时我们就只能直接修改Class文件，本文将展示在基于不同的需求通过可视化工具和Javassist库来直接对Class文件进行修改的方法。</p>

<p><strong>注：由于直接修改class文件会涉及到class文件结构的相关知识，所以利用此种方式时最好对class文件结构有一定的了解</strong></p>

<h2 id="修改class文件中的变量:4e9cb51181f8e993ffa84b42e58265dc">修改Class文件中的变量</h2>

<p>下面的代码为一个典型的输出Hello World的Java小程序</p>

<pre><code class="language-java">package com.lucumt;

public class Test {
	public static String language = &quot;Java&quot;;
	public static void main(String[] args) {
		sayHello();
	}

	public static void sayHello() {
		System.out.println(&quot;=====Hello &quot;+language+&quot; World!======&quot;);
	}
}
</code></pre>

<p>在cmd命令行中运行该程序的结果如下<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/simple-class-before-modifing-running-result.png" alt="未修改之前的运行结果" title="未修改之前的Java代码运行结果" />
<br />
若想将运行结果从 <em>Hello Java World</em> 修改为 <em>Hello Golang China</em> ，除了通过修改源代码重新编译运行这个方法之外我们还可以利用工具直接修改原有的class文件来实现。</p>

<p>首先从 <strong><a href="http://set.ee/jbe/" title="点击链接去下载jbe">JBE</a></strong> 下载 JBE(Java Bytecode Editor),JBE是一个用于浏览和修改Java Class文件的开源软件，在其官网上可以看到如下图所示的说明信息<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/jbe-official-introduction.png" alt="JBE介绍" title="JBE官方介绍" />
</p>

<p>下载完该软件后，在该软件中打开我们要修改的Class文件<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/open-class-file-using-jbe.png" alt="在JBE中打开文件" title="在JBE中打开文件" />
<br />
首先我们需要将静态变量 <em>language</em> 的值从 <em>Java</em> 修改为 <em>Golang</em>, 由于 <em>language</em> 是一个静态变量，故我们需要在class文件的 <em>clinit</em> 方法中找到该变量并修改其值。如下图所示，展开 <em>clinit</em> 并切换到Code Editor页，可以看到 <em>language</em> 的值为 <em>Java</em> ，在Code Editor部分将 <em>Java</em> 修改为 <em>Golang</em> 然后点击Save method即可完成静态变量值的修改。<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/jbe-modify-static-field.png" alt="在JBE中修改静态变量值" title="在JBE中修改静态变量值" />
<br />
接着展开 <em>sayHello</em> 方法，同样切换到Code Editor页，将 <em>World</em> 修改为 <em>China</em> 后点击Save method，至此整个修改操作完成。<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/jbe-modify-method-value.png" alt="在JBE中修改方法输出值" title="在JBE中修改方法输出值" />
</p>

<p>在命令行中重新执行该程序，输出结果为 <em>Hello Golang China</em> ，符合我们的要求。<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/simple-classa-after-modifing-running-result.png" alt="修改之后的运行结果" title="修改之后的Java代码运行结果" />
</p>

<h2 id="修改class文件中的方法:4e9cb51181f8e993ffa84b42e58265dc">修改Class文件中的方法</h2>

<p>对于较为简单的修改需求我们可以利用JBE等工具来直接修改，若要对class文件进行较为复杂的修改，如增加新方法，修改已有方法的实现逻辑等，对于此种需求虽然也可以用JBE实现目的，但工作量很大，容易出错，此时JBE已经不太适合使用，需要寻找其它更快捷的方法。</p>

<p>由于Java文件后生成的class文件是一个包含Java字节码的二进制文件，程序最终执行的就是二进制文件中的字节码，我们的需求可以归纳为如何修改Java字节码文件。前一部分通过JBE来修改class文件只不过是将这个过程进行了图形化封装，我们需要找到更底层的实现方法来适应我们的需求。</p>

<p>此时 <strong><a href="http://jboss-javassist.github.io/javassist/ Javassist官网">Javassist</a></strong> 闪亮登场！在Javassit官网关于其的第一句介绍为 <em>Javassist (Java Programming Assistant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java</em> 。Javassist天生就是为修改Java字节码而来的，它提供了源代码和字节码两种级别的API接口，为了实现的简便性，本文主要介绍利用源代码API来修改class文件。</p>

<p>下面的代码为一个计算两个整数相加的程序</p>

<pre><code class="language-java">package com.lucumt;

public class Test1 {
	public static void main(String[] args) {
          Test1 t1 = new Test1();
          int result = t1.addNumber(3, 5);
          System.out.println(&quot;result is: &quot;+result);
	}
	
	public int addNumber(int a,int b){
		return a+b;
	}
}
</code></pre>

<p>正常情况下，其输出结果如下<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/java-method-before-modifing-running-result.png" alt="未修改方法前的运行结果" title="未修改方法前的运行结果" />
<br />
若我们想将 <em>addNumber</em> 的返回结果从两个数之和变为两个数立方后求和，则可以利用Javassist提供的API通过Java程序来直接修改class文件。</p>

<p>关于如何使用Javassist，请直接参看相应的 <strong><a href="http://jboss-javassist.github.io/javassist/tutorial/tutorial.html">入门教程</a></strong> ，本文不再详细说明，利用Javassist修改 <em>addNumber</em> 的Java代码如下：</p>

<pre><code class="language-java">package com.lucumt.test;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class UpdateMethod {

	public static void main(String[] args) {
		updateMethod();
	}
	
	public static void updateMethod(){
		try {
			ClassPool cPool = new ClassPool(true);
		        //如果该文件引入了其它类，需要利用类似如下方式声明
			//cPool.importPackage(&quot;java.util.List&quot;);
			
			//设置class文件的位置
			cPool.insertClassPath(&quot;D:\\Java\\eclipse\\newworkspace\\test\\bin&quot;);
			
			//获取该class对象
			CtClass cClass = cPool.get(&quot;com.lucumt.Test1&quot;);
			
			//获取到对应的方法
			CtMethod cMethod = cClass.getDeclaredMethod(&quot;addNumber&quot;);
			
			//更改该方法的内部实现
			//需要注意的是对于参数的引用要以$开始，不能直接输入参数名称
			cMethod.setBody(&quot;{ return $1*$1*$1+$2*$2*$2; }&quot;);
			
			//替换原有的文件
			cClass.writeFile(&quot;D:\\Java\\eclipse\\newworkspace\\test\\bin&quot;);
			
			System.out.println(&quot;=======修改方法完=========&quot;);
		} catch (NotFoundException e) {
			e.printStackTrace();
		} catch (CannotCompileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}
</code></pre>

<p>运行该代码后重新执行 <em>Test1</em> 后的结果如下，从图中可以看出运行结果符合预期<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/java-method-after-modifing-runnning-result.png" alt="修改方法后的运行结果" title="修改方法后的运行结果" />
</p>

<p>关于 <em>UpdateMethod</em> 工具类有如下几点说明：</p>

<ul>
<li>如果要修改的class文件中引入了其它类，需要调用 <em>ClassPool</em> 中的 <em>importPackage</em> 方法引入该类，否则程序会报错</li>
<li>修改完后，一定要调用 <em>CtClass</em> 中的 <em>writeFile</em> 方法覆盖原有的class文件，否则修改不生效</li>
<li>在修改方法的过程中若要引用方法参数，不能在修改程序代码中直接写该参数，否则程序会抛出<em>javassist.CannotCompileException: [source error] no such field:</em> 异常。在本例中 <em>addNumber</em> 的两个参数分别为 <em>a</em> 和 <em>b</em> ，在修改时不能写成<code>cMethod.setBody(&quot;{ return a*a*a+b*b*b; }&quot;)</code>需要修改为<code>cMethod.setBody(&quot;{ return $1*$1*$1+$2*$2*$2; }&quot;)</code></li>
<li>在Javassist的 <strong><a href="http://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#intro">Introspection and customization</a></strong> 部分有如下一段话<br />
<em>The parameters passed to the target method are accessible with $1, $2, &hellip; instead of the original parameter names. $1 represents the first parameter, $2 represents the second parameter, and so on. The types of those variables are identical to the parameter types. $0 is equivalent to this. If the method is static, $0 is not available.</em><br />
从中可知，方法中的参数从 <em>$1</em> 开始，若该方法为非 <em>static</em> 方法，可以用 <em>$0</em> 来表示该方法实例自身，若该方法为 <em>static</em> 方法，则 <em>$0</em> 不可用</li>
</ul>

<h2 id="在class文件中增加方法:4e9cb51181f8e993ffa84b42e58265dc">在Class文件中增加方法</h2>

<p>Javassist不仅可以修改已有的方法，还可以给class文件增加新的方法。仍以前面的 <em>Test1</em> Java代码中为例，现要求增加一个名为 <em>showParameter</em> 的方法并在 <em>addNumber</em> 方法中调用，其主要功能是输出 <em>addNumber</em> 中传入的参数。利用Javassist修改class文件实现该功能的代码如下</p>

<pre><code class="language-java">package com.lucumt.test;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class AddMethod {

	public static void main(String[] args) {
		addMethod();
	}
	
	public static void addMethod(){
		try {
			ClassPool cPool = new ClassPool(true);
			cPool.insertClassPath(&quot;D:\\Java\\eclipse\\newworkspace\\test\\bin&quot;);
			CtClass cClass = cPool.get(&quot;com.lucumt.Test1&quot;);
			
			
			CtMethod cMethod = cClass.getDeclaredMethod(&quot;addNumber&quot;);
			
			//增加一个新方法
			String methodStr =&quot;public void showParameters(int a,int b){&quot; 
					    +&quot;  System.out.println(\&quot;First parameter: \&quot;+a);&quot;
					    +&quot;  System.out.println(\&quot;Second parameter: \&quot;+b);&quot;
					    +&quot;}&quot;;
			CtMethod newMethod = CtNewMethod.make(methodStr, cClass);
			cClass.addMethod(newMethod);
			
			//调用新增的方法
			cMethod.setBody(&quot;{ showParameters($1,$2);return $1*$1*$1+$2*$2*$2; }&quot;);
			cClass.writeFile(&quot;D:\\Java\\eclipse\\newworkspace\\test\\bin&quot;);
			
		} catch (NotFoundException e) {
			e.printStackTrace();
		} catch (CannotCompileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}
</code></pre>

<p>运行该代码后重新执行 <em>Test1</em> 后的结果如下，从图中可以看出运行结果符合预期<br />
<img src="http://lucumt.info/blog_img/modify-java-class-file-content-directly/java-add-method-runnning-result.png" alt="新增方法后的运行结果" title="新增方法后的运行结果" />
<br />
从上述代码可以看出，利用Javassist增加方法比修改方法更简单，先将要新增的方法内容赋值到字符串，然后分别调用相关类的 <em>make</em> 和 <em>addMethod</em> 方法即可。</p>

<h2 id="后记:4e9cb51181f8e993ffa84b42e58265dc">后记</h2>

<p>利用JBE或Javassist虽然可以实现直接修改class文件的内容，但毕竟属于不正规的做法，可能会导致后续版本不一致等问题，在条件允许的情况下还是要尽量通过修改Java文件然后重新编译的方式来实现目的。</p>

	
			

			

			
          </section>
		  
		  <div class="page-link">
			
			<a href="http://lucumt.info/posts/easyui-datagrid-row-not-align-when-column-frozen/">//下一篇</a>
			
			&nbsp;
			
	      </div>

          
          	<div id="disqus_thread"></div>
<script type="text/javascript">
    var disqus_shortname = 'rosenlu';
    var disqus_identifier = 'http:\/\/lucumt.info\/posts\/modify-java-class-file-content-directly\/';
    var disqus_title = '在不重新编译的情况下直接修改Java Class文件中的内容';
    var disqus_url = 'http:\/\/lucumt.info\/posts\/modify-java-class-file-content-directly\/';

    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
          
        
      <div class="footer">
	<hr class="thin" />
	<div class="pure-menu pure-menu-horizontal pure-menu-open">
		<ul class="footer-menu">
		
			<li><a href="https://github.com/tmaiaroto/hugo-redlounge">Designed by Red Lounge</a></li>
		
		</ul>
	</div>

	<p>&copy; 2017. All rights reserved.</p>
</div>
    </div>
  </div>
	
		<script type="text/javascript">
			onscroll = function() {
			  var toTopVisible = false;
			  var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
			  if (scrollTop > 1000) {
			    if (!toTopVisible) {
			      document.getElementById('nav-to-top').style.display = 'block';
			    }
			  } else {
			    if (scrollTop < 1000 || toTopVisible) {
			      document.getElementById('nav-to-top').style.display = 'none';
			    }
			  }
			};
		</script>
	

	

  
</body>
</html>